////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include "Light.hpp"

#include <cmath>

#include <SFML/OpenGL.hpp>

#include <SmegTools/OpenGL.hpp>
#include <SmegTools/Tools.hpp>

#include "../Hull/Hull.hpp"
#include "LightSystem.hpp"

#include <SmegTools/Debug/DebugNew.hpp>

namespace smeg {
    namespace light {

////////////////////////////////////////////////////////////
/// Default Constructor   
////////////////////////////////////////////////////////////
Light::Light( const sf::Color& _Color,
            const sf::Vector2f& _Center,             
            float _fAngleDeg, 
            float _fAngleSpreadDeg, 
            float _fRadius,
            bool _bIsStatic ) :
    QuadTreeOccupant( _Center, _fAngleDeg, _bIsStatic ),
    m_Color( _Color ),
    m_fAngleSpread( _fAngleSpreadDeg * math::DEG_TO_RAD ),
    m_fHalfAngleSpread( m_fAngleSpread * 0.5f ),
    m_fRadius( _fRadius ),
    m_fDotLit( -1.0f ),
    m_pStaticTexture( NULL )
#ifdef _DEBUG
    , DEBUG_ShowInside( true )
    , DEBUG_Vertices( NULL )
#endif
{
    m_fDotLit = cos( m_fHalfAngleSpread );
    ComputeAABB();

    SetStatic( m_bIsStatic );

#ifdef _DEBUG
    DebugComputeVertices();
#endif

    LightSystem::GetInstance()->AddLight( this );
}

////////////////////////////////////////////////////////////
/// Copy Constructor   
////////////////////////////////////////////////////////////
Light::Light( const Light& _Copy ) :
    QuadTreeOccupant( _Copy ),
    m_Color( _Copy.m_Color ),
    m_fAngleSpread( _Copy.m_fAngleSpread ),
    m_fHalfAngleSpread( m_fAngleSpread * 0.5f ),
    m_fRadius( _Copy.m_fRadius ),
    m_fDotLit( -1.0f ),
    m_pStaticTexture( NULL )
#ifdef _DEBUG
    , DEBUG_ShowInside( _Copy.DEBUG_ShowInside )
    , DEBUG_Vertices( NULL )
#endif
{
    m_fDotLit = cos( m_fHalfAngleSpread );
    ComputeAABB();

    SetStatic( m_bIsStatic );

#ifdef _DEBUG
    DebugComputeVertices();
#endif

    LightSystem::GetInstance()->AddLight( this );
}
    
////////////////////////////////////////////////////////////
/// Copy Constructor   
////////////////////////////////////////////////////////////
Light::~Light()
{
    delete m_pStaticTexture;
    m_pStaticTexture = NULL;
#ifdef _DEBUG
    delete[] DEBUG_Vertices;
    DEBUG_Vertices = NULL;
#endif
}

////////////////////////////////////////////////////////////
/// Compute the static texture from the given texture
////////////////////////////////////////////////////////////
void Light::ComputeStatic( const sf::RenderTexture& _LightTexture, const float* _pProjMatrix )
{
    if ( m_pStaticTexture ) {
        const sf::Vector2f& lowerBound( m_AABB.GetLowerBound() ),
            & upperBound( m_AABB.GetUpperBound() );

        m_pStaticTexture->setActive();
        tools::SetupWindowProjection( 0, 0, m_pStaticTexture->getSize().x, m_pStaticTexture->getSize().y, _pProjMatrix );

        tools::RenderTexture( &_LightTexture.getTexture(), 
                        (int)lowerBound.x, (int)lowerBound.y,
                        (int)upperBound.x, (int)upperBound.y,
                        GL_ONE, GL_ZERO );

        m_pStaticTexture->display();
    }
}

////////////////////////////////////////////////////////////
/// Render the static texture
////////////////////////////////////////////////////////////
void Light::RenderStatic() const
{
    if ( m_pStaticTexture ) {
        const sf::Vector2f& lowerBound( m_AABB.GetLowerBound() ),
            & upperBound( m_AABB.GetUpperBound() );

        tools::RenderTexture( &m_pStaticTexture->getTexture(), 
                            (int)lowerBound.x, (int)lowerBound.y,
                            (int)upperBound.x, (int)upperBound.y,
                            GL_SRC_ALPHA, GL_ONE );
    }
}

////////////////////////////////////////////////////////////
/// Is the point lit ?
////////////////////////////////////////////////////////////
bool Light::IsLit( const sf::Vector2f& _Point ) const {
    sf::Vector2f centerToPoint( _Point - m_Center );
    if ( math::Length( centerToPoint ) <= m_fRadius ) {
        math::Normalize( centerToPoint );
        if ( math::Dot( centerToPoint, m_Direction ) >= m_fDotLit ) {
            return true;
        }
    }

    return false;
}

////////////////////////////////////////////////////////////
/// Compute the AABB
////////////////////////////////////////////////////////////
void Light::ComputeAABB() {
    float f2Radius = m_fRadius * 2.0f;
    m_AABB.Setup( m_Center, sf::Vector2f( f2Radius, f2Radius ) );
    TreeUpdate();
}

/*********************** Accessors ************************/
void Light::SetAngleSpreadRad( float _fAngleSpreadRad )    {
    m_fAngleSpread = math::GetValidRadianAngle( _fAngleSpreadRad );
    m_fHalfAngleSpread = m_fAngleSpread * 0.5f;
    m_fDotLit = cos( m_fHalfAngleSpread );
    
#ifdef _DEBUG
    DebugComputeVertices();
#endif
}
    
void Light::SetAngleSpreadDeg( float _fAngleSpreadDeg )    {
    SetAngleSpreadRad( _fAngleSpreadDeg * math::DEG_TO_RAD );
}
    
void Light::SetRadius( float _fRadius ) {
    if ( _fRadius > 0.0f ) {
        m_fRadius = _fRadius;

        ComputeAABB();
    
#ifdef _DEBUG
        DebugComputeVertices();
#endif
    }
}

void Light::SetStatic( bool _bIsStatic ) {
    QuadTreeOccupant::SetStatic( _bIsStatic );
    
    if ( m_bIsStatic ) {
        if ( !m_pStaticTexture ) {
            m_pStaticTexture = new sf::RenderTexture();
            m_pStaticTexture->create( 1024, 1024 );
            m_pStaticTexture->setActive();
            glEnable( GL_BLEND );
            glEnable( GL_TEXTURE_2D );
        }
    } else {
        delete m_pStaticTexture;
        m_pStaticTexture = NULL;
    }
}

#ifdef _DEBUG
void Light::SetAngleRad( float _fAngleRad ) {
    QuadTreeOccupant::SetAngleRad( _fAngleRad );

    DebugComputeVertices();
}

void Light::RotateRad( float _fAngleRad ) {
    QuadTreeOccupant::RotateRad( _fAngleRad );

    DebugComputeVertices();
}

void Light::DebugComputeVertices() {
    float fStartAngle = m_fAngleRad - m_fHalfAngleSpread,
        fEndAngle = m_fAngleRad + m_fHalfAngleSpread,
        fInc = m_fAngleSpread / 32.f;

    if (!DEBUG_Vertices) {
        DEBUG_Vertices = new sf::Vector2f[33];
    }

    unsigned i = 0;
    for ( float a = fStartAngle; a <= fEndAngle && i < 32; a += fInc, ++i ) {
        DEBUG_Vertices[i].x = m_fRadius * cos( a );
        DEBUG_Vertices[i].y = m_fRadius * sin( a );
    }

    DEBUG_Vertices[32].x = m_fRadius * cos( fEndAngle );
    DEBUG_Vertices[32].y = m_fRadius * sin( fEndAngle );
}

void Light::DebugRender( const sf::Color& _Color ) const {
    if ( DEBUG_DebugRender && DEBUG_Vertices && m_fRadius > 0.0f ) {
        // Save position and color
        glPushMatrix();
        glPushAttrib( GL_COLOR_BUFFER_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT );

        // Setup position and color
        glTranslatef( m_Center.x, m_Center.y, 0.0f );
        glColor4ub( _Color.r, _Color.g, _Color.b, _Color.a );

        // Draw "Circle"
        glBegin( GL_LINE_STRIP );
        for ( unsigned i = 0; i <= 32; ++i ) {
            glVertex2f( DEBUG_Vertices[i].x, DEBUG_Vertices[i].y );
        }
        glEnd();

        if ( DEBUG_ShowInside ) {
            // Draw center to "Circle"
            glBegin( GL_LINES );
            for ( unsigned i = 0; i <= 32; ++i ) {
                glVertex2f( 0.0f, 0.0f );
                glVertex2f( DEBUG_Vertices[i].x, DEBUG_Vertices[i].y );
            }
            glEnd();
        } else if ( m_fAngleSpread > 0.0f && m_fAngleSpread < math::TWO_PI ) {
            glBegin( GL_LINES );
                // Draw first ...
                glVertex2f( 0.0f, 0.0f );
                glVertex2f( DEBUG_Vertices[0].x, DEBUG_Vertices[0].y );
                // ... and last line
                glVertex2f( 0.0f, 0.0f );
                glVertex2f( DEBUG_Vertices[32].x, DEBUG_Vertices[32].y );
            glEnd();
        }

        // Reset position and color
        glPopAttrib();
        glPopMatrix();
    }
}
#endif // _DEBUG

    } // namespace light
} // namespace smeg